// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "../network/post.h"
#include "network.h"
#include "interrupt.h"
#include "list.h"
#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "synch.h"
#include "ipt.h"
#include "string.h"
#include <stdio.h>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <sstream>

using namespace std;

int ClientStub(char*);

struct ProcessData {
	AddrSpace* address;
	int id;
	int numChildThreads;
	bool deleted;
	/* Child processes table? */
} ;

//Process Table stuff
vector<ProcessData*>* processTable = new vector<ProcessData*>();
int globalProcessCount = 0;
int nextProcessIndex = 0;
BitMap* swapFileMap = new BitMap(SWAP_FILE_SIZE);
Lock* swapFileMapLock = new Lock("swapFileLock");
Lock* processTableLock = new Lock("processTableLock");
Lock* threadsLock = new Lock("threadsLock");
Lock* IPTLock = new Lock("IPTLock");

//TLB/IPT stuff
int currentTLBIndex = 0;

int copyin(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes from the current thread's virtual address vaddr.
    // Return the number of bytes so read, or -1 if an error occors.
    // Errors can generally mean a bad virtual address was passed in.
    bool result;
    int n=0;			// The number of bytes copied in
    int *paddr = new int;

    while ( n >= 0 && n < len) {
      result = machine->ReadMem( vaddr, 1, paddr );
      while(!result) // FALL 09 CHANGES
	  {
   			result = machine->ReadMem( vaddr, 1, paddr ); // FALL 09 CHANGES: TO HANDLE PAGE FAULT IN THE ReadMem SYS CALL
	  }	
      
      buf[n++] = *paddr;
     
      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    delete paddr;
    return len;
}

int copyout(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes to the current thread's virtual address vaddr.
    // Return the number of bytes so written, or -1 if an error
    // occors.  Errors can generally mean a bad virtual address was
    // passed in.
    bool result;
    int n=0;			// The number of bytes copied in

    while ( n >= 0 && n < len) {
      // Note that we check every byte's address
      result = machine->WriteMem( vaddr, 1, (int)(buf[n++]) );

      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    return n;
}

void Create_Syscall(unsigned int vaddr, int len) {
    // Create the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  No
    // way to return errors, though...
    char *buf = new char[len+1];	// Kernel buffer to put the name in

    if (!buf) return;

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Create\n");
	delete buf;
	return;
    }

    buf[len]='\0';

    fileSystem->Create(buf,0);
    delete[] buf;
    return;
}

int Open_Syscall(unsigned int vaddr, int len) {
    // Open the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  If
    // the file is opened successfully, it is put in the address
    // space's file table and an id returned that can find the file
    // later.  If there are any errors, -1 is returned.
    char *buf = new char[len+1];	// Kernel buffer to put the name in
    OpenFile *f;			// The new open file
    int id;				// The openfile id

    if (!buf) {
	printf("%s","Can't allocate kernel buffer in Open\n");
	return -1;
    }

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Open\n");
	delete[] buf;
	return -1;
    }

    buf[len]='\0';

    f = fileSystem->Open(buf);
    delete[] buf;

    if ( f ) {
	if ((id = currentThread->space->fileTable.Put(f)) == -1 )
	    delete f;
	return id;
    }
    else
	return -1;
}

void Write_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one. For disk files, the file is looked
    // up in the current address space's open file table and used as
    // the target of the write.
    
    char *buf;		// Kernel buffer for output
    OpenFile *f;	// Open file for output

    if ( id == ConsoleInput) return;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer for write!\n");
	return;
    } else {
        if ( copyin(vaddr,len,buf) == -1 ) {
	    printf("%s","Bad pointer passed to to write: data not written\n");
	    delete[] buf;
	    return;
	}
    }

    if ( id == ConsoleOutput) {
      for (int ii=0; ii<len; ii++) {
	printf("%c",buf[ii]);
      }

    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    f->Write(buf, len);
	} else {
	    printf("%s","Bad OpenFileId passed to Write\n");
	    len = -1;
	}
    }

    delete[] buf;
}

int Read_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one.    We reuse len as the number of bytes
    // read, which is an unnessecary savings of space.
    char *buf;		// Kernel buffer for input
    OpenFile *f;	// Open file for output

    if ( id == ConsoleOutput) return -1;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer in Read\n");
	return -1;
    }

    if ( id == ConsoleInput) {
      //Reading from the keyboard
      scanf("%s", buf);

      if ( copyout(vaddr, len, buf) == -1 ) {
	printf("%s","Bad pointer passed to Read: data not copied\n");
      }
    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    len = f->Read(buf, len);
	    if ( len > 0 ) {
	        //Read something from the file. Put into user's address space
  	        if ( copyout(vaddr, len, buf) == -1 ) {
		    printf("%s","Bad pointer passed to Read: data not copied\n");
		}
	    }
	} else {
	    printf("%s","Bad OpenFileId passed to Read\n");
	    len = -1;
	}
    }

    delete[] buf;
    return len;
}

void Close_Syscall(int fd) {
    // Close the file associated with id fd.  No error reporting.
    OpenFile *f = (OpenFile *) currentThread->space->fileTable.Remove(fd);

    if ( f ) {
      delete f;
    } else {
      printf("%s","Tried to close an unopen file\n");
    }
}

//Forked by Exec_Syscall to start the first thread of a process
void ExecThreadHelper(int parentProcessId)
{
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("---- Entering ExecThreadHelper ----\n");
	
	threadsLock->Acquire();
	
	currentThread->space->SaveState();
	currentThread->space->InitRegisters();
	currentThread->space->RestoreState();
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("ExecThread initialized\n");
	
	threadsLock->Release();
	
	machine->Run();
}

//This function is a special case function for Executing the first nachos process
int Exec_Process(char* fileName)
{
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("==== Entering Exec_Process ====\n");

	//open nachos executable file
	OpenFile *executable = fileSystem->Open(fileName);
    AddrSpace *space;

    if (executable == NULL) {
	printf("Unable to open file %s\n", fileName);
	return -1;
    }
   
	//initialize address space
    space = new AddrSpace(executable);

	//for virtual memory we need to save this
    //delete executable;

	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("AddrSpace initialized\n");
	
	//update process table
	processTableLock->Acquire();
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Updating processTable...\n");
	
	ProcessData* nextProcessData = new ProcessData();
	
	nextProcessData->address = space;
	
	space->ownerProcessId = nextProcessIndex;
	nextProcessData->id = nextProcessIndex;	
	
	nextProcessData->numChildThreads = 1;
	
	nextProcessIndex++;
	globalProcessCount++;
	
	processTable->push_back(nextProcessData);
	processTableLock->Release();
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Creating first thread of process\n");
	
	//create first thread of process
	Thread* execThread = new Thread("execThread");
	execThread->space = space;
	execThread->parentProcessId = nextProcessData->id;

	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Forking first thread of process...\n");
	
	//fork out first thread of process
	execThread->Fork((VoidFunctionPtr)ExecThreadHelper, nextProcessData->id);
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Returning nextProcessData ID...\n");
	
	return nextProcessData->id;
}

//Executes a new process
int Exec_Syscall(int nameAddress, int nameLength)
{
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("==== Entering Exec_Syscall ====\n");

	//read filename from arguments
	char* fileName;
	if(nameLength >= 0)
	{
		fileName = new char[nameLength + 1];
		copyin(nameAddress, nameLength, fileName);
		fileName[nameLength] = '\0';
	}
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Opening file %s\n", fileName);
	
	//open nachos executable file
	OpenFile *executable = fileSystem->Open(fileName);
    AddrSpace *space;

    if (executable == NULL) {
	printf("Unable to open file %s\n", fileName);
	return -1;
    }
   
	//initialize address space
    space = new AddrSpace(executable);

	//for virtual memory we need to save this
    //delete executable;

	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("AddrSpace initialized\n");
	
	//update process table
	processTableLock->Acquire();
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Updating processTable...\n");
	
	ProcessData* nextProcessData = new ProcessData();
	
	nextProcessData->address = space;
	
	space->ownerProcessId = nextProcessIndex;
	nextProcessData->id = nextProcessIndex;	
	
	nextProcessData->numChildThreads = 1;
	
	nextProcessIndex++;
	globalProcessCount++;
	
	processTable->push_back(nextProcessData);
	processTableLock->Release();
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Creating first thread of process\n");
	
	//create first thread of process
	Thread* execThread = new Thread("execThread");
	execThread->space = space;
	execThread->parentProcessId = nextProcessData->id;

	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Forking first thread of process...\n");
	
	//fork out first thread of process
	execThread->Fork((VoidFunctionPtr)ExecThreadHelper, nextProcessData->id);
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Returning nextProcessData ID...\n");
	
	return nextProcessData->id;
}

//Forked by Fork_Syscall to start the thread
void ForkThreadHelper(int functionAddress)
{
	threadsLock->Acquire();
	
	currentThread->space->InitRegisters();
    machine->WriteRegister(StackReg, currentThread->space->getNewStackEndAddress() - 16);
	machine->WriteRegister(PCReg, functionAddress);
    machine->WriteRegister(NextPCReg, functionAddress + 4);
	currentThread->space->RestoreState();
	
	threadsLock->Release();
	
	//switch to user mode
	machine->Run();
}
  
void Fork_Syscall(int functionAddress)
{
	Thread* forkThread = new Thread("forkThread");
	
	threadsLock->Acquire();
	
	forkThread->space = currentThread->space;
	//the new thread is created under the same process, inherits addrspace
	forkThread->parentProcessId = currentThread->parentProcessId;
	
	ProcessData* parentProcessData = processTable->at(forkThread->parentProcessId);
	parentProcessData->numChildThreads++;
	
	threadsLock->Release();
	
	//fork the helper
	forkThread->Fork((VoidFunctionPtr)ForkThreadHelper, functionAddress);
}

void Exit_Syscall(int status)
{
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("==== Entering Exit_Syscall ====\n");
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Exiting with return value: %d\n", status);

	threadsLock->Acquire();
	int parentId = currentThread->parentProcessId;
	
	//special case for nachos processes
	if(parentId == -1)
	{
		if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Thread is a nachos process, kill thread immediately\n");
		
		threadsLock->Release();
		currentThread->Finish();
	}
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Accessing parent process at id %d...\n", parentId);
	
	//get the parent process
	ProcessData* parentProcessData = processTable->at(parentId);
	
	if(parentProcessData == NULL)
	{
		threadsLock->Release();
		printf("Error in Exit_Syscall--parent process not found!\n");
		interrupt->Halt();
	}
	
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Parent process has %d child threads\n", parentProcessData->numChildThreads);
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Total processeses in progress: %d\n", globalProcessCount);
	if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Terminating thread...\n");
	
	if(parentProcessData->numChildThreads == 0)
	{
		printf("Error in Exit_Syscall--parent process has no threads!\n");
		interrupt->Halt();
	}
	else if(parentProcessData->numChildThreads == 1)
	{	
		if(globalProcessCount == 1)
		{
			//last thread in last process, exit nachos
			if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("last thread in last process, exit nachos\n");
			parentProcessData->address->ReleaseAllMemory();
			delete parentProcessData->address;
			delete parentProcessData;
			globalProcessCount--;
			threadsLock->Release();
			//currentThread->Finish();
			interrupt->Halt();
		}
		else
		{
			//last thread in process, kill process
			if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("last thread in process, kill process\n");
			parentProcessData->address->ReleaseAllMemory();
			delete parentProcessData->address;
			delete parentProcessData;
			globalProcessCount--;
			threadsLock->Release();
			currentThread->Finish();
		}
	}
	else
	{
		if(globalProcessCount == 1)
		{
			//thread in last process, kill thread
			if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("thread in last process, kill thread\n");
			parentProcessData->numChildThreads--;
			threadsLock->Release();
			currentThread->Finish();
		}
		else
		{
			//thread in process, kill thread
			if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("thread in process, kill thread\n");
			parentProcessData->numChildThreads--;
			threadsLock->Release();
			currentThread->Finish();
		}
	}
}

void Yield_Syscall()
{
	currentThread->Yield();
}

const int LOCKCREATE = 3;
const int LOCKACQUIRE = 0;
const int LOCKRELEASE = 1;
const int LOCKDESTROY = 4;
const int CONDITIONWAIT = 2;
const int CONDITIONCREATE = 5;
const int CONDITIONSIGNAL = 6;
const int CONDITIONBROADCAST = 7;
const int CONDITIONDESTROY= 8;
const int MONITORCREATE = 9;
const int MONITORGET = 10;
const int MONITORSET = 11;
const int MONITORDESTROY = 12;

const int SERVER_LOC = 0;

struct LockInfo{
	Lock* lock;
	AddrSpace* lockOwner;
	bool readyToDelete;
	bool deleted;
};

const int MAX_LOCKS = 550;
int nextLock = 0;
Lock* lockArrayLock = new Lock("lockArray");
LockInfo** syscallLockArray = new LockInfo*[MAX_LOCKS];

/* int CreateLock_Syscall()
{
	if(nextLock < MAX_LOCKS){
		lockArrayLock->Acquire();
	
		int lockNum = nextLock;
		syscallLockArray[lockNum] = new LockInfo();
		
		syscallLockArray[lockNum]->lock = new Lock("Lock"+lockNum);
		syscallLockArray[lockNum]->lockOwner = currentThread->space;
		syscallLockArray[lockNum]->readyToDelete = false;
		syscallLockArray[lockNum]->deleted = false;
		
		nextLock++;
		lockArrayLock->Release();
		
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("Creating lock number %d\n",lockNum);
		return lockNum;
	}
	else{
		printf("ERROR: ALL LOCKS HAVE BEEN USED\n");
		return -1;
	}
}*/

int ClientStub(string messageTemp){
	PacketHeader outPktHdr, inPktHdr;
    MailHeader outMailHdr, inMailHdr;
	char buffer[MaxMailSize];	
	
	//PostOffice *postOffice2;
	//postOffice2 = new PostOffice(1,1, 10);
	
	
	char message[MaxMailSize];
	strcpy(message,messageTemp.c_str());
	outPktHdr.to = SERVER_LOC;
	outMailHdr.from = 0;
	outMailHdr.to = 0;
	outMailHdr.length = strlen(message) + 1;

	bool success = postOffice->Send(outPktHdr, outMailHdr, message); 
	//bool success = true;
	
	if ( !success ) {
	  printf("The postOffice Send failed. You must not have the other Nachos running. Terminating Nachos.\n");
	  interrupt->Halt();
	}

	postOffice->Receive(0, &inPktHdr, &inMailHdr, buffer);

	return atoi(buffer);
	//return 0;
}


int CreateLock_Syscall(int nameAddress,int nameLength){
	
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}
	
	stringstream ss;
	ss << LOCKCREATE << " " << name;
	string message = ss.str();
	
	int lockNum = ClientStub(message);
	
	//if(PRINT_EXCEPTION_DEBUG_MESSAGES)
		printf("Now creating lock number %d\n",lockNum);
	
	return lockNum;
}

/* void DestroyLock_Syscall(int lockIndex)
{
	lockArrayLock->Acquire();
	
	//delete syscallLockArray[lockIndex];
	if(lockIndex >= nextLock || lockIndex < 0){
		printf("INVALID LOCK NUMBER\n");
	}
	else if(syscallLockArray[lockIndex]->deleted){
		printf("Error: This lock %d has been deleted\n",lockIndex);
	}
	else if(currentThread->space == syscallLockArray[lockIndex]->lockOwner){
		syscallLockArray[lockIndex]->lock->Acquire();
		
		if(syscallLockArray[lockIndex]->lock->isQueueEmpty()){
			syscallLockArray[lockIndex]->deleted = true;
			if(PRINT_EXCEPTION_DEBUG_MESSAGES)
				printf("'Deleted' lock %d\n",lockIndex);
		}
		else{
			syscallLockArray[lockIndex]->readyToDelete = true;
			if(PRINT_EXCEPTION_DEBUG_MESSAGES)
				printf("Ready to delete lock %d\n",lockIndex);
		}
		
		syscallLockArray[lockIndex]->lock->Release();
	}
	else{
		printf("NO ACCESS TO THIS LOCK\n");
	}
	
	lockArrayLock->Release();
} */
void DestroyLock_Syscall(int nameAddress,int nameLength){
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}

	stringstream ss;
	ss << LOCKDESTROY << " " << name;
	string message = ss.str();
	
	int success = ClientStub(message);
}

/* void AcquireLock_Syscall(int lockIndex)
{
	if(lockIndex >= nextLock || lockIndex < 0){
		printf("INVALID LOCK NUMBER\n");
	}
	else if(syscallLockArray[lockIndex]->deleted){
		printf("Error: This lock %d has been deleted\n",lockIndex);
	}
	else if(currentThread->space == syscallLockArray[lockIndex]->lockOwner){
		syscallLockArray[lockIndex]->lock->Acquire();
		syscallLockArray[lockIndex]->lockOwner = currentThread->space;
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("Giving lock number %d\n",lockIndex);
	}
	else{
		printf("NO ACCESS TO THIS LOCK\n");
	}
} */

void AcquireLock_Syscall(int nameAddress,int nameLength)
{
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}

	stringstream ss;
	ss << LOCKACQUIRE << " " << name;
	string message = ss.str();

	int success = ClientStub(message);
}

/* void ReleaseLock_Syscall(int lockIndex)
{
	lockArrayLock->Acquire();
	
	if(lockIndex >= nextLock || lockIndex < 0){
		printf("INVALID LOCK NUMBER\n");
	}
	else if(syscallLockArray[lockIndex]->deleted){
		printf("Error: This lock %d has been deleted\n",lockIndex);
	}
	else if(currentThread->space == syscallLockArray[lockIndex]->lockOwner){
		syscallLockArray[lockIndex]->lock->Release();
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("Releasing lock number %d\n",lockIndex);
		
		if(syscallLockArray[lockIndex]->readyToDelete){
			syscallLockArray[lockIndex]->deleted = true;
		}
	}
	else{
		printf("NO ACCESS TO THIS LOCK\n");
	}
	
	lockArrayLock->Release();
} */

void ReleaseLock_Syscall(int nameAddress,int nameLength)
{
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}

	stringstream ss;
	ss << LOCKRELEASE << " " << name;
	string message = ss.str();
	
	int success = ClientStub(message);
}

struct ConditionInfo{
	Condition* condition;
	AddrSpace* conditionOwner;
	bool readyToDelete;
	bool deleted;
};

const int MAX_CONDITIONS = MAX_LOCKS;
int nextCondition = 0;

Lock* conditionArrayLock = new Lock("conditionArray");
ConditionInfo** syscallConditionArray = new ConditionInfo*[MAX_CONDITIONS]();

/* int CreateCondition_Syscall()
{
	if(nextCondition < MAX_CONDITIONS){
		conditionArrayLock->Acquire();
		
		int conditionNum = nextCondition;
		
		syscallConditionArray[conditionNum] = new ConditionInfo();
		
		syscallConditionArray[conditionNum]->condition = new Condition("Condition"+conditionNum);		
		syscallConditionArray[conditionNum]->conditionOwner = currentThread->space;		
		syscallConditionArray[conditionNum]->readyToDelete = false;
		syscallConditionArray[conditionNum]->deleted = false;
		
		nextCondition++;
		
		conditionArrayLock->Release();
		
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("Creating condition number %d\n",conditionNum);
				
		return conditionNum;	
	}
	else{
		printf("ERROR: ALL CONDITION VARIABLES HAVE BEEN USED\n");
		return -1;
	}
} */

int CreateCondition_Syscall(int nameAddress,int nameLength)
{
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}

	stringstream ss;
	ss << CONDITIONCREATE << " " << name;
	string message = ss.str();
	
	return ClientStub(message);
}

/* void DestroyCondition_Syscall(int condIndex)
{
	conditionArrayLock->Acquire();

	if(condIndex >= nextCondition || condIndex < 0){
		printf("INVALID CONDITION NUMBER\n");
	}
	else if(syscallConditionArray[condIndex]->deleted){
		printf("Error: This condition %d has been deleted\n",condIndex);
	}
	else if(syscallConditionArray[condIndex]->conditionOwner == currentThread->space){
		if(syscallConditionArray[condIndex]->condition->isQueueEmpty()){
			if(PRINT_EXCEPTION_DEBUG_MESSAGES)
				printf("'Deleted' condition %d\n",condIndex);
			syscallConditionArray[condIndex]->deleted = true;
		}
		else{
			syscallConditionArray[condIndex]->readyToDelete = true;
			if(PRINT_EXCEPTION_DEBUG_MESSAGES)
				printf("Ready to delete condition %d\n",condIndex);
		}				
	}
	else{
		printf("NO ACCESS TO THIS CONDITION VARIABLE\n");
	}	
	
	conditionArrayLock->Release();
} */

void DestroyCondition_Syscall(int condIndex,int condLength)
{
	char* condName;
	if(condLength >= 0)
	{
		condName = new char[condLength + 1];
		copyin(condIndex,condLength,condName);
		condName[condLength] = '\0';
	}

	stringstream ss;
	ss << CONDITIONDESTROY << " " << condIndex;
	string message = ss.str();
	
	int success = ClientStub(message);
}

/* void WaitCondition_Syscall(int condIndex, int lockIndex)
{
	if(lockIndex >= nextLock || lockIndex < 0){
		printf("INVALID LOCK NUMBER\n");
	}
	else if(condIndex >= nextCondition || condIndex < 0){
		printf("INVALID CONDITION NUMBER\n");
	}
	else if(syscallLockArray[lockIndex]->deleted){
		printf("Error: This lock %d has been deleted\n",lockIndex);
	}
	else if(syscallConditionArray[condIndex]->deleted){
		printf("Error: This condition %d has been deleted\n",condIndex);
	}
	else if(syscallLockArray[lockIndex]->lockOwner == currentThread->space && syscallConditionArray[condIndex]->conditionOwner == currentThread->space){
		
		//syscallLockArray[lockIndex]->lock->Acquire();
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("Waiting on condition number %d\n",condIndex);
		syscallConditionArray[condIndex]->condition->Wait(syscallLockArray[lockIndex]->lock);
		//syscallLockArray[lockIndex]->lock->Release();
	}
	else{
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("NO ACCESS TO THIS CONDITION VARIABLE\n");
	}
} */
void WaitCondition_Syscall(int condIndex,int condLength,int lockIndex,int lockLength)
{
	char* condName;
	if(condLength >= 0)
	{
		condName = new char[condLength + 1];
		copyin(condIndex,condLength,condName);
		condName[condLength] = '\0';
	}

	char* lockName;
	if(lockLength >= 0)
	{
		lockName = new char[lockLength + 1];
		copyin(lockIndex,lockLength,lockName);
		lockName[lockLength] = '\0';
	}
	
	stringstream ss;
	ss << CONDITIONWAIT << " " << condIndex << " " << lockIndex; 
	string message = ss.str();
	
	int success = ClientStub(message);
}

/* void SignalCondition_Syscall(int condIndex, int lockIndex)
{
	if(lockIndex >= nextLock || lockIndex < 0){
		printf("INVALID LOCK NUMBER\n");
	}
	else if(condIndex >= nextCondition || condIndex < 0){
		printf("INVALID CONDITION NUMBER\n");
	}
	else if(syscallLockArray[lockIndex]->deleted){
		printf("Error: This lock %d has been deleted\n",lockIndex);
	}
	else if(syscallConditionArray[condIndex]->deleted){
		printf("Error: This condition %d has been deleted\n",condIndex);
	}
	else if(syscallLockArray[lockIndex]->lockOwner == currentThread->space && syscallConditionArray[condIndex]->conditionOwner == currentThread->space){
		
		//syscallLockArray[lockIndex]->lock->Acquire();
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("Signalling condition number %d\n",condIndex);
		syscallConditionArray[condIndex]->condition->Signal(syscallLockArray[lockIndex]->lock);
		//syscallLockArray[lockIndex]->lock->Release();
		
		
		if(syscallConditionArray[condIndex]->readyToDelete){
			if(PRINT_EXCEPTION_DEBUG_MESSAGES)
				printf("'Deleted' condition %d\n",condIndex);
			syscallConditionArray[condIndex]->deleted = true;
		}
	}
	else{
		printf("NO ACCESS TO THIS LOCK\n");
	}
} */

void SignalCondition_Syscall(int condIndex,int condLength,int lockIndex,int lockLength)
{
	char* condName;
	if(condLength >= 0)
	{
		condName = new char[condLength + 1];
		copyin(condIndex,condLength,condName);
		condName[condLength] = '\0';
	}

	char* lockName;
	if(lockLength >= 0)
	{
		lockName = new char[lockLength + 1];
		copyin(lockIndex,lockLength,lockName);
		lockName[lockLength] = '\0';
	}
	
	stringstream ss;
	ss << CONDITIONSIGNAL << " " << condIndex << " " << lockIndex;
	string message = ss.str();
	
	int success = ClientStub(message);
}

/*oid BroadcastCondition_Syscall(int condIndex, int lockIndex)
{
	if(lockIndex >= nextLock || lockIndex < 0){
		printf("INVALID LOCK NUMBER\n");
	}
	else if(condIndex >= nextCondition || condIndex < 0){
		printf("INVALID CONDITION NUMBER\n");
	}
	else if(syscallLockArray[lockIndex]->deleted){
		printf("Error: This lock %d has been deleted\n",lockIndex);
	}
	else if(syscallConditionArray[condIndex]->deleted){
		printf("Error: This condition %d has been deleted\n",condIndex);
	}
	else if(syscallLockArray[lockIndex]->lockOwner == currentThread->space && syscallConditionArray[condIndex]->conditionOwner == currentThread->space){
		//syscallLockArray[lockIndex]->lock->Acquire();
		if(PRINT_EXCEPTION_DEBUG_MESSAGES)
			printf("Broadcast to condition number %d\n",condIndex);
		syscallConditionArray[condIndex]->condition->Broadcast(syscallLockArray[lockIndex]->lock);
		//syscallLockArray[lockIndex]->lock->Release();
		
		
		if(syscallConditionArray[condIndex]->readyToDelete){
			if(PRINT_EXCEPTION_DEBUG_MESSAGES)
				printf("'Deleted' condition %d\n",condIndex);
			syscallConditionArray[condIndex]->deleted = true;
		}
	}
	else{
		printf("NO ACCESS TO THIS LOCK\n");
	}
}*/

void BroadcastCondition_Syscall(int condIndex,int condLength,int lockIndex,int lockLength)
{
	char* condName;
	if(condLength >= 0)
	{
		condName = new char[condLength + 1];
		copyin(condIndex,condLength,condName);
		condName[condLength] = '\0';
	}

	char* lockName;
	if(lockLength >= 0)
	{
		lockName = new char[lockLength + 1];
		copyin(lockIndex,lockLength,lockName);
		lockName[lockLength] = '\0';
	}
	
	stringstream ss;
	ss << CONDITIONBROADCAST << " " << condName << " " << lockName;
	string message = ss.str();
	
	int success = ClientStub(message);
}

int CreateMV_Syscall(int length,int nameAddress,int nameLength){

	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}

	stringstream ss;
	ss << MONITORCREATE << " " << name << " " << length;
	string message = ss.str();
	
	return ClientStub(message);
}

int GetMV_Syscall(int nameAddress,int nameLength,int arrayIndex){
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}
	
	stringstream ss;
	ss << MONITORGET << " " << name << " " << arrayIndex;
	string message = ss.str();
	
	return ClientStub(message);
}

void SetMV_Syscall(int nameAddress,int nameLength,int arrayIndex,int value){
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}
	
	stringstream ss;
	ss << MONITORSET << " " << name << " " << arrayIndex << " " << value;
	string message = ss.str();
	
	int success = ClientStub(message);
}

void DestroyMV_Syscall(int nameAddress,int nameLength){
	char* name;
	if(nameLength >= 0)
	{
		name = new char[nameLength + 1];
		copyin(nameAddress,nameLength,name);
		name[nameLength] = '\0';
	}

	stringstream ss;
	ss << MONITORDESTROY << " " << name;
	string message = ss.str();
	
	int success = ClientStub(message);
}

void FPrint_Syscall(int messageAddress, int messageLength, int numArguments, int arguments)
{	
	//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("==== Entering FPrint_Syscall ====\n");

	char* message;
	if(messageLength >= 0)
	{
		message = new char[messageLength + 1];
		copyin(messageAddress, messageLength, message);
		message[messageLength] = '\0';
	}
	
	//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("Message is: ");
	//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf(message);
	
	//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("\nArguments are: %d\n", arguments);
	
	if(numArguments == 0)
	{
		printf(message);
	}
	else if(numArguments == 1)
	{
		int arg1 = arguments % 100;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg1: %d\n", arg1);
		printf(message, arg1);
	}
	else if(numArguments == 2)
	{
		int arg1 = arguments / 100;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg1: %d\n", arg1);
		int arg2 = arguments % 100;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg2: %d\n", arg2);
		printf(message, arg1, arg2);
	}
	else if(numArguments == 3)
	{
		int arg1 = arguments / 10000;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg1: %d\n", arg1);
		arguments = arguments % 10000;
		int arg2 = arguments / 100;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg2: %d\n", arg2);
		int arg3 = arguments % 100;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg3: %d\n", arg3);
		printf(message, arg1, arg2, arg3);
	}
	else if(numArguments == 4)
	{
		int arg1 = arguments / 1000000;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg1: %d\n", arg1);
		arguments = arguments % 1000000;
		int arg2 = arguments / 10000;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg2: %d\n", arg2);
		arguments = arguments % 10000;
		int arg3 = arguments / 100;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg3: %d\n", arg3);
		int arg4 = arguments % 100;
		//if(PRINT_EXCEPTION_DEBUG_MESSAGES) printf("arg4: %d\n", arg4);
		printf(message, arg1, arg2, arg3, arg4);
	}
	else
	{
		printf("ERROR PRINTING!  numArguments is %d", numArguments);
	}
}

int RandInt_Syscall()
{
	return rand();
}

void printTLB()
{
	for(int i = 0; i < TLBSize; i++)
	{
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("tlb[%d]:\t", i);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("virtualPage == %d\t", machine->tlb[i].virtualPage);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("physicalPage == %d\t", machine->tlb[i].physicalPage);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("valid == %d\t", machine->tlb[i].valid);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("use == %d\t", machine->tlb[i].use);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("dirty == %d\n", machine->tlb[i].dirty);
	}
}

void printIPT()
{
	for(int i = 0; i < NumPhysPages; i++)
	{
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("IPT[%d]:  \t", i);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("virtualPage == %d\t", IPT[i]->virtualPage);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("physicalPage == %d\t", IPT[i]->physicalPage);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("valid == %d\t", IPT[i]->valid);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("use == %d\t", IPT[i]->use);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("dirty == %d\n", IPT[i]->dirty);
	}
}

//helper for HandlePageFaultException to copy data to TLB
void populateNextTLBEntry(unsigned int virtualPage)
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	
	//if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Populating TLB entry %d with virtual page %d\n", currentTLBIndex, virtualPage);
	
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("tlb[%d]:\t", currentTLBIndex);
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("virtualPage == %d\t", machine->tlb[currentTLBIndex].virtualPage);
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("physicalPage == %d\t", machine->tlb[currentTLBIndex].physicalPage);
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("valid == %d\t", machine->tlb[currentTLBIndex].valid);
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("use == %d\t", machine->tlb[currentTLBIndex].use);
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("dirty == %d\n", machine->tlb[currentTLBIndex].dirty);
	
	if(machine->tlb[currentTLBIndex].valid)
	{
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Propagating dirty bit of virtual page %d physical page %d at TLB index %d\n", 
				machine->tlb[currentTLBIndex].virtualPage, machine->tlb[currentTLBIndex].physicalPage, currentTLBIndex);
		
		IPT[machine->tlb[currentTLBIndex].physicalPage]->dirty = machine->tlb[currentTLBIndex].dirty;
		//IPT[machine->tlb[currentTLBIndex].physicalPage]->dirty = true;
			
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("IPT[%d]:  \t", machine->tlb[currentTLBIndex].physicalPage);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("virtualPage == %d\t", IPT[machine->tlb[currentTLBIndex].physicalPage]->virtualPage);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("physicalPage == %d\t", IPT[machine->tlb[currentTLBIndex].physicalPage]->physicalPage);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("valid == %d\t", IPT[machine->tlb[currentTLBIndex].physicalPage]->valid);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("use == %d\t", IPT[machine->tlb[currentTLBIndex].physicalPage]->use);
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("dirty == %d\n", IPT[machine->tlb[currentTLBIndex].physicalPage]->dirty);
	}
	
	machine->tlb[currentTLBIndex].virtualPage = currentThread->space->pageTable[virtualPage].virtualPage;
	machine->tlb[currentTLBIndex].physicalPage = currentThread->space->pageTable[virtualPage].physicalPage;
	machine->tlb[currentTLBIndex].valid = currentThread->space->pageTable[virtualPage].valid;
	machine->tlb[currentTLBIndex].use = currentThread->space->pageTable[virtualPage].use;
	machine->tlb[currentTLBIndex].dirty = currentThread->space->pageTable[virtualPage].dirty;
	machine->tlb[currentTLBIndex].readOnly = currentThread->space->pageTable[virtualPage].readOnly;
	
	currentTLBIndex = (currentTLBIndex + 1) % TLBSize;
	
	(void) interrupt->SetLevel(oldLevel);
}

//helper for evictAndAllocatePage to evict data from TLB
void invalidateTLBEntry(int physicalPageToEvict)
{
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Invalidating physical page %d from TLB\n", physicalPageToEvict);
	
	if(physicalPageToEvict == -1)
	{
		printf("ERROR: Physical page %d is not valid for eviction!\n", physicalPageToEvict);
		interrupt->Halt();
	}
	else
	{	
		IntStatus oldLevel = interrupt->SetLevel(IntOff);		
		
		//if the page to evict is valid and is in the TLB, then copy the dirty bit to the IPT and invalidate the TLB entry
		for(int i = 0; i < TLBSize; i++)
		{
			if(machine->tlb[i].valid && machine->tlb[i].physicalPage == physicalPageToEvict)
			{				
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("tlb[%d]:\t", i);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("virtualPage == %d\t", machine->tlb[i].virtualPage);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("physicalPage == %d\t", machine->tlb[i].physicalPage);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("valid == %d\t", machine->tlb[i].valid);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("use == %d\t", machine->tlb[i].use);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("dirty == %d\n", machine->tlb[i].dirty);
			
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Propagating dirty bit of virtual page %d physical page %d at TLB index %d\n", 
						machine->tlb[i].virtualPage, machine->tlb[i].physicalPage, i);
		
				IPT[physicalPageToEvict]->dirty = machine->tlb[i].dirty;
				//IPT[machine->tlb[i].physicalPage]->dirty = true;
				
				machine->tlb[i].valid = false;
										
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("IPT[%d]:  \t", machine->tlb[i].physicalPage);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("virtualPage == %d\t", IPT[machine->tlb[i].physicalPage]->virtualPage);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("physicalPage == %d\t", IPT[machine->tlb[i].physicalPage]->physicalPage);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("valid == %d\t", IPT[machine->tlb[i].physicalPage]->valid);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("use == %d\t", IPT[machine->tlb[i].physicalPage]->use);
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("dirty == %d\n", IPT[machine->tlb[i].physicalPage]->dirty);
			}
		}
		
		(void) interrupt->SetLevel(oldLevel);
	}
}

//helper for HandleIPTMiss to evict a page from the IPT and return the open location
int evictAndAllocatePage()
{
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Entering evictAndAllocatePage...\n");

	//select page to evict
	int physicalPageToEvict = -1;
	int virtualPageToEvict = -1;
	
	while(physicalPageToEvict == -1)
	{
		IPTLock->Acquire();
	
		//default use random eviction
		physicalPageToEvict = rand() % NumPhysPages;
		
		if(pageEvictionPolicy == EVICT_OLDEST_PAGE)
		{
			int oldestPage = 0;
			for(int i = 1; i < NumPhysPages; i++)
			{
				if(IPT[i]->timestamp < IPT[oldestPage]->timestamp)
				{
					oldestPage = i;
				}
			}
			
			physicalPageToEvict = oldestPage;
		}
		
		if(IPT[physicalPageToEvict]->use)
		{
			physicalPageToEvict = -1;
		}
		else
		{
			//evictable physical page found, store virtual page for later use
			virtualPageToEvict = IPT[physicalPageToEvict]->virtualPage;
			
			IPT[physicalPageToEvict]->use = true;
			
			if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Evicting physical page %d virtual page %d\n", physicalPageToEvict, virtualPageToEvict);
			
			processTable->at(IPT[physicalPageToEvict]->pid)->address->spaceLock->Acquire();
			
			IPTLock->Release();
		}
	}
	
	//if the page to evict belongs to the current process and is in the TLB, then copy the dirty bit to the IPT and invalidate the TLB entry
	invalidateTLBEntry(physicalPageToEvict);
	
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printTLB();
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printIPT();
	
	//update page table entry for evicted page by setting valid to false
	currentThread->space->pageTable[virtualPageToEvict].valid = false;
	
	//if the data has been changed, write it to the swap file, otherwise don't bother
	if(IPT[physicalPageToEvict]->dirty)
	{
		//search for space in swap file
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Searching for open location in swap file...\n");
		
		int nextSwapFileAddress = -1;
		
		if(currentThread->space->pageTable[virtualPageToEvict].offset == -1)
		{
			if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Virtual page %d has never been written to the swap file, allocating space for it...\n",
					virtualPageToEvict);
		
			//if the page has never been written to the swap file, allocate swap file space for it
			swapFileMapLock->Acquire();
			int nextSwapFilePage = swapFileMap->Find();
			swapFileMapLock->Release();
		
			if(nextSwapFilePage == -1)
			{
				printf("Swap file is out of space!\n");
				interrupt->Halt();
			}
			else
			{
				if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Next free swap file page is %d\n", nextSwapFilePage);
			
				//convert a swap file page to address
				nextSwapFileAddress = nextSwapFilePage * PageSize;
			}
		}
		else
		{
			if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Virtual page %d has been written to the swap file before, using old offset %d\n",
					virtualPageToEvict, currentThread->space->pageTable[virtualPageToEvict].offset);
		
			//if the page has been written to the swap file before, reuse it
			nextSwapFileAddress = currentThread->space->pageTable[virtualPageToEvict].offset;
		}
		
		//perform file writing operations
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Writing page to swap file at offset %d\n", nextSwapFileAddress);
		
		int physicalAddress = physicalPageToEvict * PageSize;
		swapFile->WriteAt(&(machine->mainMemory[physicalAddress]), PageSize, nextSwapFileAddress);
		
		//update page table with swap file location
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Swap file write successful, updating page table location...\n");
		currentThread->space->pageTable[virtualPageToEvict].offset = nextSwapFileAddress;
		currentThread->space->pageTable[virtualPageToEvict].location = IN_SWAP_FILE;
	}
	
	processTable->at(IPT[physicalPageToEvict]->pid)->address->spaceLock->Release();
	
	//now that page is evicted, return it for freed use
	return physicalPageToEvict;
}

//If there is an IPT miss, allocate new memory in IPT (using swap file if necessary) and load the data in, returning the physical page
int HandleIPTMiss(unsigned int requestedVirtualPage)
{
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Handling IPT miss for virtual page %d\n", requestedVirtualPage);

	currentThread->space->mapLock->Acquire();
	int nextFreePage = currentThread->space->pageMap.Find();
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Next free physical page is %d\n", nextFreePage);
	currentThread->space->mapLock->Release();
	
	//no free physical memory, evict an IPT entry
	if(nextFreePage == -1)
	{
		//comment this out when virtual memory is implemented
		//printf("Halting nachos until we have a swap file to get more pages...\n");
		//interrupt->Halt();
		
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Out of physical pages, looking for page to evict\n");
		
		//store the evicted page index for later updating
		nextFreePage = evictAndAllocatePage();
	}
	
	currentThread->space->spaceLock->Acquire();
	
	//there is now free physical memory, copy page into main memory
	if(currentThread->space->pageTable[requestedVirtualPage].location == IN_EXECUTABLE)
	{
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Requested page %d found in executable at offset %d\n", requestedVirtualPage, 
				currentThread->space->pageTable[requestedVirtualPage].offset);
		
		int physicalAddress = nextFreePage * PageSize;
		currentThread->space->execFile->ReadAt(&(machine->mainMemory[physicalAddress]), PageSize, currentThread->space->pageTable[requestedVirtualPage].offset);	
	}
	else if(currentThread->space->pageTable[requestedVirtualPage].location == IN_SWAP_FILE)
	{
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Requested page %d found in swap file at offset %d\n", requestedVirtualPage,
				currentThread->space->pageTable[requestedVirtualPage].offset);
		
		int physicalAddress = nextFreePage * PageSize;
		
		swapFile->ReadAt(&(machine->mainMemory[physicalAddress]), PageSize, currentThread->space->pageTable[requestedVirtualPage].offset);
		
		//currentThread->space->pageTable[requestedVirtualPage].location = NOT_ON_DISK;
	}
	else
	{
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("Requested page not on disk, write operation desired\n");
	}

	//update page table with newly copied page
	currentThread->space->pageTable[requestedVirtualPage].virtualPage = requestedVirtualPage;	
	currentThread->space->pageTable[requestedVirtualPage].physicalPage = nextFreePage;
	currentThread->space->pageTable[requestedVirtualPage].valid = true;
	currentThread->space->pageTable[requestedVirtualPage].use = false;
	currentThread->space->pageTable[requestedVirtualPage].dirty = false;
	currentThread->space->pageTable[requestedVirtualPage].readOnly = false;
	
	currentThread->space->spaceLock->Release();
	
	//update IPT with newly copied page
	IPT[nextFreePage]->virtualPage = requestedVirtualPage;
	IPT[nextFreePage]->physicalPage = nextFreePage;
	IPT[nextFreePage]->valid = true;
	IPT[nextFreePage]->dirty = true;
	IPT[nextFreePage]->readOnly = false;
	IPT[nextFreePage]->pid = currentThread->parentProcessId;
	
	//update timestamp with system time for future eviction
	IPT[nextFreePage]->timestamp = stats->userTicks;
	
	return nextFreePage;
}

void HandlePageFaultException(int badVirtualAddress)
{
	unsigned int badVirtualPage = badVirtualAddress / PageSize;
	
	if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("\nHandling PageFaultException for virtual page %d\n", badVirtualPage);
	
	AddrSpace *space = currentThread->space;
	
	//erroneous virtual address requested
	if(badVirtualPage > (space->numPages))
	{
		printf("ERROR: Virtual page %d not found!\n", badVirtualPage);
		interrupt->Halt();
	}
	
	//TODO add lock back in
	IPTLock->Acquire();
	
	//search IPT for virtual page
	int physicalPage = -1;
	for(int i = 0; i < NumPhysPages; i++)
	{
		if(IPT[i]->valid && IPT[i]->virtualPage == badVirtualPage 
				&& IPT[i]->pid == currentThread->parentProcessId && !IPT[i]->use)
		{			
			physicalPage = i;
			IPT[physicalPage]->use = true;
			
			//TODO add lock back in
			IPTLock->Release();
			
			if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printf("VirtualPage %d found in IPT with physical page %d\n", badVirtualPage, physicalPage);
			
	
			break;
		}
	}
			
	
	//handle IPT miss
	if(physicalPage == -1)
	{
		//TODO add lock back in
		IPTLock->Release();
	
		physicalPage = HandleIPTMiss(badVirtualPage);
		
		populateNextTLBEntry(badVirtualPage);
		
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printTLB();
		if(PRINT_PAGE_FAULT_DEBUG_MESSAGES) printIPT();
	}
	else
	{
		populateNextTLBEntry(badVirtualPage);	
	}
	
	IPT[physicalPage]->use = false;
}

void ExceptionHandler(ExceptionType which) 
{
	//PageFaultException is handled using the bad virtual address read from the register
	if ( which == PageFaultException ) 
	{
		HandlePageFaultException(machine->ReadRegister(BadVAddrReg));
	}
	//SyscallException is handled using whichever syscall is demanded
    else if ( which == SyscallException ) 
	{
	
		int type = machine->ReadRegister(2); // Which syscall?
		int rv=0; 	// the return value from a syscall
	
		switch (type) {
			default:
			DEBUG('a', "Unknown syscall - shutting down.\n");
			case SC_Halt:
			DEBUG('a', "Shutdown, initiated by user program.\n");
			interrupt->Halt();
			break;
			case SC_Create:
			DEBUG('a', "Create syscall.\n");
			Create_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
			break;
			case SC_Open:
			DEBUG('a', "Open syscall.\n");
			rv = Open_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
			break;
			case SC_Write:
			DEBUG('a', "Write syscall.\n");
			Write_Syscall(machine->ReadRegister(4),
					  machine->ReadRegister(5),
					  machine->ReadRegister(6));
			break;
			case SC_Read:
			DEBUG('a', "Read syscall.\n");
			rv = Read_Syscall(machine->ReadRegister(4),
					  machine->ReadRegister(5),
					  machine->ReadRegister(6));
			break;
			case SC_Close:
			DEBUG('a', "Close syscall.\n");
			Close_Syscall(machine->ReadRegister(4));
			break;
			
			case SC_Exec:
			DEBUG('a', "Exec syscall.\n");
			rv = Exec_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
			break;
			
			case SC_Fork:
			DEBUG('a', "Fork syscall.\n");
			Fork_Syscall(machine->ReadRegister(4));
			break;

			case SC_Exit:
			DEBUG('a', "Exit syscall.\n");
			Exit_Syscall(machine->ReadRegister(4));
			break;
			
			case SC_Yield:
			DEBUG('a', "Yield syscall.\n");
			Yield_Syscall();
			break;
			
			case SC_CreateLock:
			DEBUG('a', "CreateLock syscall.\n");
			rv = CreateLock_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			
			case SC_DestroyLock:
			DEBUG('a', "DestroyLock syscall.\n");
			DestroyLock_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			
			case SC_AcquireLock:
			DEBUG('a', "AcquireLock syscall.\n");
			AcquireLock_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			
			case SC_ReleaseLock:
			DEBUG('a', "ReleaseLock syscall.\n");
			ReleaseLock_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			
			case SC_CreateCondition:
			DEBUG('a', "CreateCondition syscall.\n");
			rv = CreateCondition_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			
			case SC_DestroyCondition:
			DEBUG('a', "DestroyCondition syscall.\n");
			DestroyCondition_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			
			case SC_WaitCondition:
			DEBUG('a', "WaitCondition syscall.\n");
			WaitCondition_Syscall(machine->ReadRegister(4), machine->ReadRegister(5),machine->ReadRegister(6),machine->ReadRegister(7));
			break;
			
			case SC_SignalCondition:
			DEBUG('a', "SignalCondition syscall.\n");
			SignalCondition_Syscall(machine->ReadRegister(4), machine->ReadRegister(5),machine->ReadRegister(6),machine->ReadRegister(7));
			break;
			
			case SC_BroadcastCondition:
			DEBUG('a', "BroadcastCondition syscall.\n");
			BroadcastCondition_Syscall(machine->ReadRegister(4), machine->ReadRegister(5),machine->ReadRegister(6),machine->ReadRegister(7));
			break;
			
			case SC_CreateMV:
			DEBUG('a', "Monitor Variable Create syscall.\n");
			rv = CreateMV_Syscall(machine->ReadRegister(4),machine->ReadRegister(5),machine->ReadRegister(6));
			break;
			
			case SC_GetMV:
			DEBUG('a', "Monitor Variable Get syscall.\n");
			rv = GetMV_Syscall(machine->ReadRegister(4),machine->ReadRegister(5),machine->ReadRegister(6));
			break;
			
			case SC_SetMV:
			DEBUG('a', "Monitor Variable Set syscall.\n");
			SetMV_Syscall(machine->ReadRegister(4),machine->ReadRegister(5),machine->ReadRegister(6),machine->ReadRegister(7));
			break;
			
			case SC_DestroyMV:
			DEBUG('a', "Monitor Variable Destroy syscall.\n");
			DestroyMV_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			
			case SC_FPrint:
			DEBUG('a', "FPrint syscall.\n");
			FPrint_Syscall(machine->ReadRegister(4), machine->ReadRegister(5), machine->ReadRegister(6), machine->ReadRegister(7));
			break;
			
			case SC_RandInt:
			DEBUG('a', "RandInt syscall.\n");
			rv = RandInt_Syscall();
			break;
		}

		// Put in the return value and increment the PC
		machine->WriteRegister(2,rv);
		machine->WriteRegister(PrevPCReg,machine->ReadRegister(PCReg));
		machine->WriteRegister(PCReg,machine->ReadRegister(NextPCReg));
		machine->WriteRegister(NextPCReg,machine->ReadRegister(PCReg)+4);
		return;
    } 
	else 
	{
      cout<<"Unexpected user mode exception - which: "<<which<<endl;
      interrupt->Halt();
    }
}
